#!/usr/bin/python
####################################################################
#Programmers:  Danil Dotsenko, dd@accentsolution.com
#Date: 19/08/2006
#Filename: installertasks.py
#Version: 0.2
#
#
#       Copyleft 2006 Danil Dotsenko
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
#
####################################################################

from commands import getoutput
import os
import re

# Set it True or False to see more messages in the console.
# function survives better than variable when importing py as modules.
def debug():
	return True

###########################################################################
# Setting up the command execution engine
# this is a command Abstractor - allows testing without Karamba

# import karamba
def execute(command):
	# karamba.execute(command)
	# output = None
	# this command holds until the command performs.
	getoutput(command)

def executeInteractive(command):
	# not sure what the exuivalent Karamba funtion will be.
	return getoutput(command)
#
###########################################################################

###########################################################################
# These just make it easier to send messages through kdialog.
#
def showmessage(message, caption=None, appName='Installer'):
	'''
	message(message, caption='', appName='Installer')
	'''
	if not caption: # if that option is not defined in ini, we may get it None from there as well.
		caption = ''
	# swap of appName and caption in the command IS intentional.
	myCommand = 'kdialog --caption "'+appName+'" --title "'+caption+'" --msgbox "'+message+'"'
	execute(myCommand)

def textin(message, defaulttext='', caption=None, appName='Installer'):
	'''
	textin(message, defaulttext='', caption=None, appName='Installer')
	message - explanatory text above the text box.
	defaulttext - text to be put inside the textbox
	caption - title bar caption. Usually explains action.
	'''
	if not caption: 
		# if that option is not defined in ini, we may get it None from there as well.
		# but sometimes, ppl WANT caption to be ''.
		caption = ''
	myCommand = 'kdialog --caption "'+appName+'" --title "'+caption+'" --textinputbox "'+message+'" "'+defaulttext+'"'
	output = executeInteractive(myCommand)
	if output:
		return output
	else:
		return "quit" # this sets %output% = 'quit' which triggers appletClose(self)

def presentchoice(message, choices, caption=None, appName='Installer'):
	'''
	choice(choices, message, caption='', appName='Installer'):
	message - any text.
	choices - array-looking string. Ex: "['key1','value1','key2','vaue2']"
	caption - Short message for title bar.
	appName - App Name for title bar.
	'''
	# swap of appName and caption in the command IS intentional.
	if not caption: # if that option is not defined in ini, we may get it None from there as well.
		caption = ''
	optionString = ''
	exec 'choices = '+choices # this is now an array
	for choice in choices:
		optionString = optionString+' "'+choice+'"'
	myCommand = 'kdialog --caption "'+appName+'" --title "'+caption+'" --menu "'+message+'"'+optionString
	dirtyoutput = executeInteractive(myCommand)
	# on some 'broken' distros console / inut commands produce 'noise'
	# we will ty to filter for that now.
	output = "quit" # this sets %output% = 'quit' which triggers appletClose(self)
	# we are hoping that further manipulations will find the goodies and set output to something usefull
	# we are doing it this way because on may 'broken' systems, kdialog is followed by heaps of trash in console.
	trash = dirtyoutput.splitlines() # now it's an array with lines of output
	if trash.__len__() > 0:
		# lets create a search mask out of possible answers
		answers = []
		for i in range(int(choices.__len__()/2)):
			answers.append(choices[i*2]) # we are getting every second string in choices - answer keys
		srchMask = re.compile('|'.join(answers))
		# now we run through all the lines (most likely only 1) and check that our output is there.
		for line in trash:
			if srchMask.match(line):
				output = srchMask.match(line).group(0)
	return output 
#
##########################################################

##########################################################
# Unpacking actions
# Zip is assumed.
def unzip(source, items, target):
	'''
	unpack(source, items, target)
	source - zip (skz) file
	itmes - array-looking string with items in the zip file you need to extract
	target - path where to extract the items.
	'''
	myCommand = ['unzip', '-j', '-o', source]
	if items.__contains__('['):
		exec 'items = '+items
	else:
		items = [items]
	myCommand.extend(items)
	myCommand.append('-d')
	myCommand.append(target)
	if not os.path.exists(target): # the demn unzip doesn't want to create needed folders.
		os.makedirs(target) # so we will make them ourselves
	os.spawnvp(os.P_WAIT, 'unzip', myCommand)

def manipulateconfig(action, settings, cfgObject):
	'''
	ini(iniFile, settings, cfgObject):

	action - one of methods of cfgObject in localconf (set, get, load, save)
	settings - several choices here
	  for set 'section,option,value'
	  for get 'section,option' and returns the value of course.
	  for load '/path/to/file/to/load.ini'
	  for save '/path/to/file/to/save.ini'
	cfgObject - Pointer to cfgObject created by load.
	'''

	action = action.split(' ',1)[1]

	if action == 'clear':
		#It is uncertain if this one works, use with cautioin
		cfgObject = localconf.cfgObject()

	elif action == 'load':
		cfgObject.load(settings)

	elif action == 'save':
		cfgObject.save(settings)

	elif action == 'get':
		# import localconf
		# 1st we brake down the settings. It is in ['section,option','section2,option2',...] format.
		value = []
		section, option = settings.split(',',1)
		value.append(cfgObject.get(section, option))
		return value

	elif action == 'set':
		section, option, value = settings.split(',',2)
		cfgObject.set(section, option, value)

def task(actiontype, settings, installerVars=None):
	'''
	This is a wrapper for the functions above.
	It takes actiontype and all the options in the section
	and converts that into actionable function above.
	If another rendering engine is implemented, I would expect
	this function to remain, but point to different functions.

	actiontype - name of action as used in ini file
	   (May be compound name like "config get".
	    We are using .split() before matching to command list.
	    After the command is matched, each IF knows what to do with tail)
	settings - dictionary type with all contents of that section
	installerVars - ini may contain %HOME%-like variables.
	    This var holds a dictionary type with all these variables.
	'''

	# Bellow listed are substitudes for these:
	#
	# message(message, caption=None, appName='Installer'):
	# choice(choices, message, caption=None, appName='Installer'):
	# unzip(source, items, target):
	# config(action, settings, cfgObject):

	# this dictionary holds pairs of "ini action type" = "function we use for that"
	# the functions are in thes same file and are listed above.
	# this way you can switch which function manages which action alsmost on-fly
	action2function = {
		'message':'showmessage',
		'inputbox':'textin',
		'choice':'presentchoice',
		'extract':'unzip',
		'config':'manipulateconfig',
		}

	# now, this is a list of variables the function takes
	# note that variables are the keywords used in the actual ini.
	# order is important! that's the order the functions take the args.
	#
	# Note: (as in case of "unzip") we have some variables we need to expand in the strings.
	# process them on as-needed basis in the IF cases bellow.
	# Also, some vars in installerVars are "special" We don't replace them
	# in text mode. We = them to the value, like %cfgObject%

	function2args = {
		'showmessage':['text', 'title'],
		'textin':['text', 'value', 'title'],
		'presentchoice':['text', 'value', 'title'],
		'unzip':['%sourceskz%', 'value', 'target'],
		'manipulateconfig':['actiontype', 'value', '%cfgObject%'],
		}

	# now, we need to split compound actiontypes into main action, subordinate action.
	if debug():
		print "Karambino here: actiontype is", actiontype
	action = actiontype.split(' ',1)[0] # splitting obly 1 time at 1st space char and using 1st piece

	# Assembling collection of actiontypes to match.
	# we get something like that "asdf|zxcv|qwer"
	srchMask = re.compile('|'.join(action2function.keys()))
	if srchMask.match(action):
		# convert action name into function name
		function = action2function[action]
		# getting required arguments list for that function
		arguments = function2args[function]
		# substituding the "required argument keywords" for values from ini and system vars.
		for argNumber in range(arguments.__len__()):
			argument = arguments[argNumber]
			# special cases:
			if argument[0] == '%' and argument[-1] == '%':
				# system variable used directly, not in string
				# needed for things like %cfgObject% - which is a pointer to cfg object, not string
				arguments[argNumber]=installerVars[argument]
			elif settings.__contains__(argument):
				arguments[argNumber]=replaceVariables(unicode(settings[argument]), installerVars)
			else:
				arguments[argNumber]=''
		# now we have arguments[] prepared for function's consumption.
		# lets prepare the command for use with "exec"
		# since the number of arguments can be different, lets use this algorythm to pile up the argument references.
		argRefs = [] # we will get somehting like ['arguments[0]','arguments[1]'], where "arguments" is the actual variable we used above.
		for i in range(arguments.__len__()):
			argRefs.append('arguments['+str(i)+']')
		argRefs = ', '.join(argRefs) # now we have 'arguments[0], arguments[1]' string
		command = 'output = '+function + '(' + argRefs + ')'
		exec command
		if output:
			installerVars['%output%']=output
		return installerVars

def replaceVariables(string, installerVars):
	'''
	This function looks for %var% types of variables in the string and replaces them with corresponding content from installerVars
	'''
	variables = installerVars.keys()
	srchMask = re.compile('|'.join(variables))
	while srchMask.search(string):
		variable = srchMask.search(string).group(0)
		string = string.replace(variable, installerVars[variable])
	return string

if __name__ == '__main__':
	pass
